package core;

/**
 * Represents a Barcode, which is either a UPC or a unique Item Barcode
 *
 */
public class Barcode extends ModelObject implements Comparable<Barcode> {

	private static final long serialVersionUID = 9088757558959301372L;
	
	/** The String representation of this Barcode */
	private String _stringRep;
	
	/** The type of this Barcode */
	private BarcodeType _type;
	
	/**
	 * Constructs a Barcode
	 * @param stringRep The String representation of the Barcode, which must be non-empty
	 * @param type The type of the Barcode
	 * @throws IllegalArgumentException if the type is null or the String does not represent a
	 * UPC-A barcode.
	 * 
	 * @pre stringRep != null and conforms to the UPC-A specification, type != null
	 * @post a valid Barcode is constructed
	 */
	protected Barcode(String stringRep, BarcodeType type) throws IllegalArgumentException {
		assert(stringRep != null && !(stringRep.isEmpty()));
		assert(type != null);
		if (type == BarcodeType.ITEM) {
			try {
				Long.parseLong(stringRep);
			}
			catch (NumberFormatException e) {
				assert(false);
			}
			assert(stringRep.length() == 11);
			/*
			 * this next check must be done because, otherwise, the previous tests would accept
			 * "-1234567890", for instance, due to the behavior of Long.parseLong
			 */
			assert(stringRep.charAt(0) != '-');
			assert(stringRep.charAt(0) == '4');
			_stringRep = stringRep + calcCheckDigit(stringRep);
		}
		else {
			_stringRep = stringRep;
		}
		
		_type = type;
	}
	
	public static boolean isValidItemBarcode(String code) {
		if(code == null || code.isEmpty()) {
			return false;
		}
		
		try{
			Long.parseLong(code);
		}
		catch (NumberFormatException e) {
			return false;
		}
		if (code.length() != 12) return false;
		if (code.charAt(0) == '-') return false;
		if (code.charAt(0) != '4') return false;
		return true;
	}

	/**
	 * 
	 * @return the String representation of this Barcode
	 * 
	 * @pre none.
	 * @post returns a non-empty string
	 */
	protected String getString() {
		return _stringRep;
	}

	/**
	 * 
	 * @return the type of this Barcode
	 * 
	 * @pre none.
	 * @post see return statement
	 */
	protected BarcodeType getType() {
		return _type;
	}
	
	/**
	 * 
	 * @return a number representation of this Barcode.  
	 * This method is invalid for Product barcodes.
	 * 
	 * @pre none.
	 * @post see return statement
	 */
	protected long getNumberRepresentation() {
		assert(_type == BarcodeType.ITEM);
		//the constructor ensures this will not fail
		/*
		 * TODO: Does this suggest that ItemBarcode and ProductBarcode 
		 * should be two separate classes?
		 */
		return Long.parseLong(_stringRep);
	}

	@Override
	public int compareTo(Barcode arg0) {
		return this._stringRep.compareTo(arg0._stringRep);
	}

	@Override
	public int hashCode() {
		return _stringRep.hashCode();
	}
	
	private String calcCheckDigit(String barCode) {
		int answer, index;
		
		//add digits in the odd-numbered positions, multiply by 3
		answer = 0;
		for (index = 0; index < barCode.length(); index += 2) {
			answer += charToInt(barCode.charAt(index));
		}
		answer *= 3;
		
		//add digits in the even-numbered positions
		for (index = 1; index < barCode.length(); index += 2) {
			answer += charToInt(barCode.charAt(index));
		}
		
		//take result modulo 10
		answer = answer % 10;
		
		//if not zero, subtract result from 10
		if(answer != 0) {
			answer = 10 - answer;
		}
		
		return answer + "";
	}
	
	private int charToInt(char c) {
		return Character.getNumericValue(c);
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Barcode other = (Barcode) obj;
		return _stringRep.equals(other._stringRep);
	}
	
	public String toString() {
		return _stringRep;
	}
	
}
